/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package encrypt;

import java.io.IOException;
import java.io.InputStream;

/**
 *
 * @author XuanHung
 */




public class CipherInputStream
{
        BlockCipher currentCipher;
        InputStream bi;
        byte[] buffer;
        byte[] enc;
        int blockSize;
        int pos;

        /*
         * We cannot use java.io.BufferedInputStream, since that is not available in
         * J2ME. Everything could be improved alot here.
         */

        final int BUFF_SIZE = 2048;
        byte[] input_buffer = new byte[BUFF_SIZE];
        int input_buffer_pos = 0;
        int input_buffer_size = 0;

        public CipherInputStream(BlockCipher tc, InputStream bi)
        {
                this.bi = bi;
                changeCipher(tc);
        }

        private int fill_buffer() throws IOException
        {
                input_buffer_pos = 0;
                input_buffer_size = bi.read(input_buffer, 0, BUFF_SIZE);
                return input_buffer_size;
        }

        private int internal_read(byte[] b, int off, int len) throws IOException
        {
                if (input_buffer_size < 0)
                        return -1;

                if (input_buffer_pos >= input_buffer_size)
                {
                        if (fill_buffer() <= 0)
                                return -1;
                }
               
                int avail = input_buffer_size - input_buffer_pos;
                int thiscopy = (len > avail) ? avail : len;

                System.arraycopy(input_buffer, input_buffer_pos, b, off, thiscopy);
                input_buffer_pos += thiscopy;

                return thiscopy;
        }

        public void changeCipher(BlockCipher bc)
        {
                this.currentCipher = bc;
                blockSize = bc.getBlockSize();
                buffer = new byte[blockSize];
                enc = new byte[blockSize];
                pos = blockSize;
        }

        private void getBlock() throws IOException
        {
                int n = 0;
                while (n < blockSize)
                {
                        int len = internal_read(enc, n, blockSize - n);
                        if (len < 0)
                                throw new IOException("Cannot read full block, EOF reached.");
                        n += len;
                }

                try
                {
                        currentCipher.transformBlock(enc, 0, buffer, 0);
                }
                catch (Exception e)
                {
                        throw new IOException("Error while decrypting block.");
                }
                pos = 0;
        }

        public int read(byte[] dst) throws IOException
        {
                return read(dst, 0, dst.length);
        }

        public int read(byte[] dst, int off, int len) throws IOException
        {
                int count = 0;

                while (len > 0)
                {
                        if (pos >= blockSize)
                                getBlock();

                        int avail = blockSize - pos;
                        int copy = Math.min(avail, len);
                        System.arraycopy(buffer, pos, dst, off, copy);
                        pos += copy;
                        off += copy;
                        len -= copy;
                        count += copy;
                }
                return count;
        }

        public int read() throws IOException
        {
                if (pos >= blockSize)
                {
                        getBlock();
                }
                return buffer[pos++] & 0xff;
        }

        public int readPlain(byte[] b, int off, int len) throws IOException
        {
                if (pos != blockSize)
                        throw new IOException("Cannot read plain since crypto buffer is not aligned.");
                int n = 0;
                while (n < len)
                {
                        int cnt = internal_read(b, off + n, len - n);
                        if (cnt < 0)
                                throw new IOException("Cannot fill buffer, EOF reached.");
                        n += cnt;
                }
                return n;
        }
}

